package com.jjz.id.config;

import java.io.Serializable;
import java.util.Objects;
import java.util.function.Function;

import com.jjz.id.common.cipher.PasswordCipher;
import com.jjz.id.config.properties.JueAuthorizationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;


/**
 * @author ZJL
 * @create 2025-05-28-13:18
 */

@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfiguration {
    public static final String SYSTEM_SECURITY_CONTEXT_KEY = "SYSTEM_SECURITY_CONTEXT";
    public static final String SYSTEM_SAVED_REQUEST_KEY = "SYSTEM_SECURITY_SAVED_REQUEST";
    private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
    private static final String SYSTEM_ANT_PATH = "/system/**";
    private static final String API_ANT_PATH = "/api/**";
    private static final String SYSTEM_REMEMBER_ME = "sys-remember-me";
    private static final String REMEMBER_ME = "remember-me";

    public SecurityConfiguration() {
    }

    /**
     * 权限配置
     * @return
     */
    @Bean
    PermissionEvaluator permissionEvaluator() {
        return new ResourcePermissionEvaluator();
    }

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setPermissionEvaluator(this.permissionEvaluator());
        return handler;
    }

    /**
     * 集群共享配置
     * SpringSessionBackedSessionRegistry 是 Spring Security 和 Spring Session 桥接集成的一个组件，用于在 集群环境下实现会话共享与并发控制（如“踢人”）
     * @param repository
     * ✅ 支持跨节点共享 Session（存 Redis、Jdbc、MongoDB）
     * ✅ 支持按用户名（FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME）查找 Session
     * ✅ 实现了 Spring Security 的 SessionRegistry 接口，用于：
     * 查看用户已登录的所有 Session
     * 实现“单用户登录”、“强制下线”等控制逻辑
     * @return
     */
    @Bean
    public SessionRegistry sessionRegistry(FindByIndexNameSessionRepository repository) {
        return new SpringSessionBackedSessionRegistry(repository);
    }

    static class ResourcePermissionEvaluator implements PermissionEvaluator {


        public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
            System.out.println(" 忽略权限 check, todo xxxxxxxxxxxxxxx!");
            return true;
        }

        public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
            throw new IllegalArgumentException("not supported!");
        }
    }

    @Configuration(
            proxyBeanMethods = false
    )
    public static class OAuth2SecurityConfiguration {
        @Bean
        @Order(-2147483645)
        SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http,
                                                       CaptchaCodeService captchaCodeService,
                                                       PasswordCipher passwordCipher, @Qualifier("oAuth2TokenRepository") PersistentTokenRepository persistentTokenRepository,
                                                       @Qualifier("oAuthUserDetailService") OAuth2UserDetailsService oAuth2UserDetailsService,
                                                       @Qualifier("oAuth2UserCreateByPhoneService") OAuth2UserCreateService oAuth2UserCreateService,
                                                       @Qualifier("oAuthUserDetailServicePhone") OAuth2UserDetailsService oAuth2UserDetailsServicePhone,
                                                       @Qualifier("authorizationServerSecurityFilterChain") SecurityFilterChain securityFilterChain,
                                                       UserPasswordManager userPasswordManager, UserTraceService userTraceService,
                                                       RememberMeAuthenticationSuccessHandler rememberMeAuthenticationSuccessHandler) throws Exception {
            DefaultSecurityFilterChain authorizationServerFilterChain = (DefaultSecurityFilterChain)securityFilterChain;
            SimpleAuthenticationEntryPoint authenticationEntryPoint = new SimpleAuthenticationEntryPoint();
            AuthenticationEntryPointFailureHandler authenticationFailureHandler = new IdentityAuthenticationFailureHandler(authenticationEntryPoint, userTraceService);
            RedirectLoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler = new RedirectLoginAuthenticationSuccessHandler(userTraceService);
            HttpSecurity var10000 = http.securityMatcher(new AndRequestMatcher(new RequestMatcher[]{new NegatedRequestMatcher(new AntPathRequestMatcher("/api/**")), new NegatedRequestMatcher(new AntPathRequestMatcher("/system/**")), new NegatedRequestMatcher(authorizationServerFilterChain.getRequestMatcher())})).authorizeHttpRequests((authorizeRequests) -> {
                ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)authorizeRequests.requestMatchers(new String[]{"/captcha/login/sms", "/captcha/register/sms", "/actuator/health", "/component/**", "/js/**", "/pear.config.yml", "/account.config.yml", "/admin/css/**", "/admin/fonts/**", "/admin/js/**", "/admin/images/**", "/images/**", "/favicon.ico", "/register", "/css/**", "/assets/**", "/login", "/captcha/**"})).permitAll().anyRequest()).authenticated();
            }).csrf((t) -> {
                t.disable();
            }).cors((t) -> {
            }).headers((t) -> {
                t.frameOptions((f) -> {
                    f.sameOrigin();
                });
                t.contentSecurityPolicy((contentSecurityPolicyConfig) -> {
                    contentSecurityPolicyConfig.policyDirectives("frame-ancestors 'self' *.nuzar.com;");
                });
            });
            Objects.requireNonNull(oAuth2UserDetailsService);
            var10000.userDetailsService(oAuth2UserDetailsService::loadOAuth2UserByUsername).formLogin((t) -> {
                ((FormLoginConfigurer)((FormLoginConfigurer)t.loginPage("/login").successHandler(loginAuthenticationSuccessHandler)).failureHandler(authenticationFailureHandler)).permitAll();
            }).sessionManagement((t) -> {
                t.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
            }).logout((t) -> {
                t.deleteCookies(new String[]{"remember-me"}).clearAuthentication(true).permitAll();
            }).addFilterBefore(new LoginRecordFilter(), UsernamePasswordAuthenticationFilter.class).addFilterBefore(new NuzarBehaviorVerificationFilter("/login", captchaService), UsernamePasswordAuthenticationFilter.class).addFilterBefore(new NuzarPasswordDecryptFilter(passwordCipher, "/login"), UsernamePasswordAuthenticationFilter.class).addFilterBefore(new NuzarPasswordForceChangeFilter("/login", userPasswordManager, authenticationFailureHandler), UsernamePasswordAuthenticationFilter.class).rememberMe((rememberMeConfigurer) -> {
                RememberMeConfigurer var10000 = rememberMeConfigurer.tokenRepository(persistentTokenRepository).tokenValiditySeconds(604800);
                Objects.requireNonNull(oAuth2UserDetailsService);
                var10000.userDetailsService(oAuth2UserDetailsService::loadOAuth2UserByUsername).authenticationSuccessHandler(rememberMeAuthenticationSuccessHandler);
            }).oauth2Login((oauth2Login) -> {
                oauth2Login.loginPage("/login").successHandler(new FederatedIdentityAuthenticationSuccessHandler());
            }).with(new LoginFilterSecurityConfigurer(), (t) -> {
                t.captchaLogin((c) -> {
                    SmsLoginFilterConfigurer var10000 = c.captchaService((phone, code, device) -> {
                        return captchaCodeService.verifySmsCaptcha(device, phone, code, CaptchaCategory.LOGIN);
                    });
                    Objects.requireNonNull(oAuth2UserDetailsServicePhone);
                    var10000 = var10000.captchaUserDetailsService(oAuth2UserDetailsServicePhone::loadOAuth2UserByUsername);
                    Objects.requireNonNull(oAuth2UserCreateService);
                    ((SmsLoginFilterConfigurer)var10000.captchaUserCreateService(oAuth2UserCreateService::create).successHandler(loginAuthenticationSuccessHandler)).failureHandler(authenticationFailureHandler);
                });
            });
            return (SecurityFilterChain)http.build();
        }

        public OAuth2SecurityConfiguration() {
        }
    }

    @Configuration(
            proxyBeanMethods = false
    )
    public static class ApiSecurityConfiguration {
        public ApiSecurityConfiguration() {
        }

        JwtAuthenticationConverter jwtAuthenticationConverter() {
            IdentityJwtTokenAuthoritiesConverter authoritiesConverter = new IdentityJwtTokenAuthoritiesConverter();
            JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
            authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
            return authenticationConverter;
        }

        @Bean
        @Order(-2147483646)
        SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
            http.securityMatcher(new String[]{"/api/**"}).authorizeHttpRequests((t) -> {
                ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)t.requestMatchers(new String[]{"/**"})).hasAnyAuthority(new String[]{"SCOPE_identity.api", "ROLE_administrator"});
            }).csrf((t) -> {
                t.disable();
            }).cors((t) -> {
            }).oauth2ResourceServer((t) -> {
                t.jwt((j) -> {
                    j.jwtAuthenticationConverter(this.jwtAuthenticationConverter());
                });
            });
            return (SecurityFilterChain)http.build();
        }
    }

    @Configuration(
            proxyBeanMethods = false
    )
    public static class SystemSecurityConfiguration {
        public SystemSecurityConfiguration() {
        }

        @Bean
        @Order(-2147483646)
        SecurityFilterChain systemSecurityFilterChain(HttpSecurity http, PasswordCipher passwordCipher, CaptchaService captchaService, @Qualifier("oAuthSystemUserDetailService") OAuth2UserDetailsService oAuth2UserDetailsService, @Qualifier("oAuth2SystemTokenRepository") PersistentTokenRepository persistentTokenRepository, UserTraceService userTraceService, RememberMeAuthenticationSuccessHandler rememberMeAuthenticationSuccessHandler) throws Exception {
            SimpleAuthenticationEntryPoint authenticationEntryPoint = new SimpleAuthenticationEntryPoint();
            AuthenticationEntryPointFailureHandler authenticationFailureHandler = new IdentityAuthenticationFailureHandler(authenticationEntryPoint, userTraceService);
            HttpSessionSecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
            securityContextRepository.setSpringSecurityContextKey("SYSTEM_SECURITY_CONTEXT");
            HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
            requestCache.setSessionAttrName("SYSTEM_SECURITY__SAVED_REQUEST");
            HttpSecurity var10000 = http.securityMatcher(new String[]{"/system/**"}).csrf((t) -> {
                t.disable();
            }).headers((t) -> {
                t.frameOptions((f) -> {
                    f.sameOrigin();
                });
            }).requestCache((t) -> {
                t.requestCache(requestCache);
            }).securityContext((t) -> {
                t.securityContextRepository(securityContextRepository);
            }).authorizeHttpRequests((t) -> {
                ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)t.anyRequest()).hasRole("administrator");
            }).addFilterBefore(new LoginRecordFilter(), UsernamePasswordAuthenticationFilter.class).addFilterBefore(new NuzarBehaviorVerificationFilter("/system/login", captchaService), UsernamePasswordAuthenticationFilter.class).addFilterBefore(new NuzarPasswordDecryptFilter(passwordCipher, "/system/login"), UsernamePasswordAuthenticationFilter.class).rememberMe((rememberMeConfigurer) -> {
                RememberMeConfigurer var10000 = rememberMeConfigurer.tokenRepository(persistentTokenRepository).tokenValiditySeconds(604800).rememberMeCookieName("sys-remember-me");
                Objects.requireNonNull(oAuth2UserDetailsService);
                var10000.userDetailsService(oAuth2UserDetailsService::loadOAuth2UserByUsername).authenticationSuccessHandler(rememberMeAuthenticationSuccessHandler);
            });
            Objects.requireNonNull(oAuth2UserDetailsService);
            var10000.userDetailsService(oAuth2UserDetailsService::loadOAuth2UserByUsername).logout((logoutConfigurer) -> {
                logoutConfigurer.logoutUrl("/system/logout").deleteCookies(new String[]{"sys-remember-me"}).permitAll();
            }).formLogin((t) -> {
                ((FormLoginConfigurer)((FormLoginConfigurer)((FormLoginConfigurer)t.loginPage("/system/login").loginProcessingUrl("/system/login")).successHandler(new RedirectLoginAuthenticationSuccessHandler("/system", requestCache, userTraceService))).failureHandler(authenticationFailureHandler)).permitAll();
            });
            return (SecurityFilterChain)http.build();
        }
    }

    @Configuration(
            proxyBeanMethods = false
    )
    @EnableConfigurationProperties({JueAuthorizationProperties.class})
    public static class AuthorizationServerConfiguration {
        private static final Logger log = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);

        public AuthorizationServerConfiguration() {
        }

        @Bean({"authorizationServerSecurityFilterChain"})
        @Order(-2147483647)
        SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, @Qualifier("oAuthUserDetailService") OAuth2UserDetailsService oAuth2UserDetailsService, OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) throws Exception {
            OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
            authorizationServerConfigurer.authorizationEndpoint((authorizationEndpointConfigurer) -> {
                authorizationEndpointConfigurer.consentPage("/oauth2/consent");
            });
            authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {
                tokenEndpoint.accessTokenRequestConverter(new PasswordAuthenticationConverter());
            });
            authorizationServerConfigurer.deviceVerificationEndpoint((deviceVerificationEndpointConfigurer) -> {
                deviceVerificationEndpointConfigurer.consentPage("/oauth2/consent");
            });
            Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = (context) -> {
                OidcUserInfoAuthenticationToken authentication = (OidcUserInfoAuthenticationToken)context.getAuthentication();
                JwtAuthenticationToken principal = (JwtAuthenticationToken)authentication.getPrincipal();
                return new OidcUserInfo(((Jwt)principal.getToken()).getClaims());
            };
            RequestMatcher authorizationServerEndpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
            HttpSecurity var10000 = ((HttpSecurity)http.securityMatcher(authorizationServerEndpointsMatcher).authorizeHttpRequests((t) -> {
                ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)t.anyRequest()).authenticated();
            }).cors((t) -> {
            }).headers((t) -> {
                t.frameOptions((f) -> {
                    f.disable();
                });
            }).csrf((csrf) -> {
                csrf.ignoringRequestMatchers(new RequestMatcher[]{authorizationServerEndpointsMatcher});
            }).formLogin((t) -> {
            }).logout((t) -> {
                t.deleteCookies(new String[]{"remember-me"});
                t.clearAuthentication(true);
                t.permitAll();
            }).with(authorizationServerConfigurer, (t) -> {
                t.oidc((oidcConfigurer) -> {
                    oidcConfigurer.userInfoEndpoint((userInfoEndpointConfigurer) -> {
                        userInfoEndpointConfigurer.userInfoMapper(userInfoMapper);
                    });
                    oidcConfigurer.logoutEndpoint((logoutEndpointConfigurer) -> {
                        logoutEndpointConfigurer.errorResponseHandler((request, response, exception) -> {
                            log.error("logout error: {}", exception);
                            OAuth2Error error = ((OAuth2AuthenticationException)exception).getError();
                            response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString());
                        }).logoutResponseHandler(new OidcLogoutAuthenticationSuccessHandler("remember-me"));
                    });
                });
            })).oauth2ResourceServer((t) -> {
                t.jwt((jwtConfigurer) -> {
                });
            });
            Objects.requireNonNull(oAuth2UserDetailsService);
            var10000.userDetailsService(oAuth2UserDetailsService::loadOAuth2UserByUsername);
            DefaultSecurityFilterChain securityFilterChain = (DefaultSecurityFilterChain)http.build();
            AuthenticationManager authenticationManager = (AuthenticationManager)http.getSharedObject(AuthenticationManager.class);
            http.authenticationProvider(new PasswordAuthenticationProvider(authorizationService, authenticationManager, tokenGenerator));
            return securityFilterChain;
        }

        @Bean
        public AuthorizationServerSettings authorizationServerSettings(JueAuthorizationProperties jueAuthorizationProperties, @Value("${server.port}") Integer port) {
            return jueAuthorizationProperties.getIssuer() == null ? AuthorizationServerSettings.builder().issuer("http://localhost:" + port).build() : AuthorizationServerSettings.builder().issuer(jueAuthorizationProperties.getIssuer()).build();
        }
    }
}
